# 异常
# Python使用称为异常 的特殊对象来管理程序执行期间发生的错误。
# 每当发生让Python不知所措的错误时，它都会创建一个异常对象。
# 如果你编写了处理该异常的代码，程序将继续运行；如果未对异常
# 进行处理，程序将停止并显示traceback，其中包含有关异常的报
# 告。
# 异常是使用try-except 代码块处理的。try-except 代码块让
# Python执行指定的操作，同时告诉Python发生异常时怎么办。使
# 用try-except 代码块时，即便出现异常，程序也将继续运行：显
# 示你编写的友好的错误消息，而不是令用户迷惑的traceback。

# 处理ZeroDivisionError 异常
# 下面来看一种导致Python引发异常的简单错误。你可能知道，不能
# 用数除以0，但还是让Python这样做：
# division_calculator.py
# print(5/0)
# # Python无法这样做，因此你将看到一个traceback
# 指出的错误ZeroDivisionError 是个异常对象。Python无法按你的要求做时，就会创建这种对象。
# 在这种情况下，Python将停止运行程序，并指出引发了哪种异常，而我们
# 可根据这些信息对程序进行修改。下面来告诉Python，发生这种错
# 误时怎么办。这样，如果再次发生此类错误，我们就有备无患了。

# 使用try-except 代码块
# 当你认为可能会发生错误时，可编写一个try-except 代码块来处
# 理可能引发的异常。你让Python尝试运行一些代码，并告诉它如果这些代码引发了指定的异常该怎么办。
# 处理ZeroDivisionError 异常的try-except 代码块类似于下面这样：
try:
	print(5/0)
except ZeroDivisionError:				# 注意错误类型一定要符合Traceback的结果，大小写严格，否则报错
	print("You can't divide by zero!")

# 将导致错误的代码行print(5/0) 放在一个try 代码块中。如果
# try 代码块中的代码运行起来没有问题，Python将跳过except 代
# 码块；如果try 代码块中的代码导致了错误，Python将查找与之匹
# 配的except 代码块并运行其中的代码。
# 在本例中，try 代码块中的代码引发了ZeroDivisionError 异
# 常，因此Python查找指出了该怎么办的except 代码块，并运行其
# 中的代码。这样，用户看到的是一条友好的错误消息，而不是traceback

# 如果try-except 代码块后面还有其他代码，程序将接着运行，因
# 为已经告诉了Python如何处理这种错误。下面来看一个捕获错误后
# 程序继续运行的示例。


# 使用异常避免崩溃
# 发生错误时，如果程序还有工作尚未完成，妥善地处理错误就尤其
# 重要。这种情况经常会出现在要求用户提供输入的程序中；如果程
# 序能够妥善地处理无效输入，就能再提示用户提供有效输入，而不至于崩溃。
# 下面来创建一个只执行除法运算的简单计算器：
# division_calculator.py

# print("Give me two numbers, and I'll divide them.")
# print("Enter 'q' to quit.")

# while True:
# 	first_number = input("\nFirst number: ")
# 	if first_number == 'q':
# 		break
# 	second_number = input("\nSecond number: ")
# 	if second_number == 'q':
# 		break
# 	answer = int(first_number) / int(second_number)
# 	print(answer)

# 程序提示用户输入一个数，并将其赋给变量
# first_number 。如果用户输入的不是表示退出的q ，就再提示用
# 户输入一个数，并将其赋给变量second_number,接下来，计算这两个数的商
# 该程序没有采取任何处理错误的措施，因此在执行除数为0的除法运算时，它将崩溃
# 程序崩溃可不好，但让用户看到traceback也不是个好主意。不懂技
# 术的用户会被搞糊涂，怀有恶意的用户还会通过traceback获悉你不
# 想他知道的信息。例如，他将知道你的程序文件的名称，还将看到
# 部分不能正确运行的代码。有时候，训练有素的攻击者可根据这些
# 信息判断出可对你的代码发起什么样的攻击。


# else 代码块
# 通过将可能引发错误的代码放在try-except 代码块中，可提高程
# 序抵御错误的能力。错误是执行除法运算的代码行导致的，因此需
# 要将它放到try-except 代码块中。这个示例还包含一个else 代码
# 块。依赖try 代码块成功执行的代码都应放到else 代码块中：
'''
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
	first_number = input("\nFirst number: ")
	if first_number == 'q':
		break
	second_number = input("\nSecond number: ")
	if second_number == 'q':
		break

	try:
		answer = int(first_number) / int(second_number)
	except ZeroDivisionError:
		print("You can't divide by 0!")
	except (ValueError, TypeError):			
	# 注意except语句可以有多个，处理的错误类型也可以有多种，但需使用元组囊括，也可加上as取别名
		print("You deosn't enter a correct number!")
	else:
		print(answer)
'''
# try-except-else 代码块的工作原理大致如下。Python尝试执
# 行try 代码块中的代码，只有可能引发异常的代码才需要放在try
# 语句中。有时候，有一些仅在try 代码块成功执行时才需要运行的
# 代码，这些代码应放在else 代码块中。except 代码块告诉
# Python，如果尝试运行try 代码块中的代码时引发了指定的异常该
# 怎么办。
# 通过预测可能发生错误的代码，可编写健壮的程序。它们即便面临
# 无效数据或缺少资源，也能继续运行，从而抵御无意的用户错误和
# 恶意的攻击。



# 处理FileNotFoundError 异常
# 使用文件时，一种常见的问题是找不到文件：查找的文件可能在其
# 他地方，文件名可能不正确，或者这个文件根本就不存在。对于所
# 有这些情形，都可使用try-except 代码块以直观的方式处理。
# 我们来尝试读取一个不存在的文件。下面的程序尝试读取文件
# alice1.txt的内容，但该文件没有存储在alice.py所在的目录中：
# alice.py
'''
filename = 'alice1.txt'

with open(filename, encoding='utf-8') as f:
	contents = f.read()
'''
# 相比于本章前面的文件打开方式，这里有两个不同之处。一是使用
# 变量f 来表示文件对象，这是一种常见的做法。二是给参
# 数encoding 指定了值，在系统的默认编码与要读取文件使用的编
# 码不一致时，必须这样做。
# Python无法读取不存在的文件，因此它引发一个FileNotFoundError异常

# FileNotFoundError 异常，这是Python找不到要打开的文件时创建的异常。
# 在本例中，这个错误是函数open() 导致的。因此，要处理这个错误，必须将try 语句放
# 在包含open() 的代码行之前：
filename = 'alice1.txt'

try:
	with open(filename, encoding='utf-8') as f:
		contents = f.read()
except FileNotFoundError:
	print(f"Sorry, the file {filename} does not exist.")

# 本例中，try 代码块引发了FileNotFoundError 异常，因此
# Python找到与该错误匹配的except 代码块，并运行其中的代码。
# 最终的结果是显示一条友好的错误消息，而不是traceback

# 如果文件不存在，这个程序就什么都做不了，错误处理代码也意义
# 不大。下面来扩展这个示例，看看在你使用多个文件时，异常处理
# 可提供什么样的帮助。



# 分析文本
# 你可以分析包含整本书的文本文件。很多经典文学作品都是简单以
# 文本文件的形式提供的，因为它们不受版权限制。本节使用的文本
# 来自古登堡计划，该计划提供了一系列不受版权限制的文学作品。
# 如果你要在编程项目中使用文学文本，这是一个很不错的资源。
# 下面来提取童话《爱丽丝漫游奇境记》（Alice in Wonderland ）的
# 文本，并尝试计算它包含多少个单词。我们将使用方法split() ，
# 它能根据一个字符串创建一个单词列表。下面是对只包含童话
# 名"Alice in Wonderland" 的字符串调用方法split() 的结果

# >>> title = "Alice in Wonderland"
# >>> title.split()
# ['Alice', 'in', 'Wonderland']

# 方法split() 以空格为分隔符将字符串分拆成多个部分，并将这些
# 部分都存储到一个列表中。结果是一个包含字符串中所有单词的列
# 表，虽然有些单词可能包含标点。为计算《爱丽丝漫游奇境记》包
# 含多少个单词，我们将对整篇小说调用split() ，再计算得到的列
# 表包含多少个元素，从而确定整篇童话大致包含多少个单词：

filename = 'alice.txt'

try:
	with open(filename, encoding='utf-8') as f:
		contents = f.read()
except FileNotFoundError:
	print(f"Sorry, the file {filename} does not exist.")
else:
	# 计算该文件大致包含多少个单词
	words = contents.split()
	num_word = len(words)
	print(f"The file {filename} has about {num_word} words.")
# 对变量contents （它现在是一个长长的字符串，包含童话《爱丽丝漫游奇境记》的全部文本）
# 调用方法split() ，以生成一个列表，其中包含这部童话中的所有单词。使用len() 来确
# 定这个列表的长度时，就能知道原始字符串大致包含多少个单词了
# 打印一条消息，指出文件包含多少个单词。这些代码都放在else 代码块中，
# 因为仅当try 代码块成功执行时才执行它们。输出指出了文件alice.txt包含多少个单词
# 这个数稍大一点，因为使用的文本文件包含出版商提供的额外信
# 息，但还是成功估算出了童话《爱丽丝漫游奇境记》的篇幅



# 使用多个文件
# 下面多分析几本书。这此之前，先将这个程序的大部分代码移到一
# 个名为count_words() 的函数中。这样，对多本书进行分析时将更容易：
# word_count.py

def count_words(filename):
	"""计算一个文件大致包含多少个单词。"""
	try:
		with open(filename, encoding='utf-8') as f:
			contents = f.read()
	except FileNotFoundError:
		print(f"Sorry, the file {filename} does not exist.")
	else:
		words = contents.split()
		num_words = len(words)
		print(f"The file {filename} has about {num_words} words.")
filename = 'alice.txt'
count_words(filename)

# 这些代码大多与原来一样，只是移到了函数count_words() 中，并增加了缩进量。
# 现在可以编写一个简单的循环，计算要分析的任何文本包含多少个
# 单词了。为此，将要分析的文件的名称存储在一个列表中，然后对
# 列表中的每个文件调用count_words() 。我们将尝试计算《爱丽
# 丝漫游奇境记》《悉达多》（Siddhartha ）、《白鲸》（Moby Dick
# ）和《小妇人》（Little Women ）分别包含多少个单词，它们都不
# 受版权限制。我故意没有将alice1.txt放到word_count.py所在的
# 目录中，从而展示该程序在文件不存在时应对得有多出色：
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt', 'alice1.txt']
for filename in filenames:
	count_words(filename)
# 文件alice1.txt不存在，但这丝毫不影响该程序处理其他文件
# 使用try-except 代码块提供了两个重要的优点：避免
# 用户看到traceback，以及让程序继续分析能够找到的其他文件。如
# 果不捕获因找不到alice1.txt而引发的FileNotFoundError 异
# 常，用户将看到完整的traceback，而程序将在尝试分析该文件
# 后停止运行。它根本不会分析《白鲸》和《小妇人》。


# 静默失败
# 在前一个示例中，我们告诉用户有一个文件找不到。但并非每次捕
# 获到异常都需要告诉用户，有时候你希望程序在发生异常时保持静
# 默，就像什么都没有发生一样继续运行。要让程序静默失败，可像
# 通常那样编写try 代码块，但在except 代码块中明确地告诉
# Python什么都不要做。Python有一个pass 语句，可用于让Python在
# 代码块中什么都不要做：

def count_words(filename):
	"""计算一个文件大致包含多少个单词。"""
	try:
		with open(filename, encoding='utf-8') as f:
			contents = f.read()
	except FileNotFoundError:
		pass					# 让程序静默失败(Error)
	else:
		words = contents.split()
		num_words = len(words)
		print(f"The file {filename} has about {num_words} words.")
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt', 'alice1.txt']
for filename in filenames:
	count_words(filename)

# 现在，出现FileNotFoundError 异常时，将执行except 代码
# 块中的代码，但什么都不会发生。这种错误发生时，不会出现
# traceback，也没有任何输出。用户将看到存在的每个文件包含多少
# 个单词，但没有任何迹象表明有一个文件未找到

# pass 语句还充当了占位符，提醒你在程序的某个地方什么都没有
# 做，并且以后也许要在这里做些什么。例如，在这个程序中，我们
# 可能决定将找不到的文件的名称写入文件missing_files.txt中。用户
# 看不到这个文件，但我们可以读取它，进而处理所有找不到文件的
# 问题。


# 决定报告哪些错误
# 该在什么情况下向用户报告错误？又该在什么情况下静默失败呢？
# 如果用户知道要分析哪些文件，他们可能希望在有文件却没有分析
# 时出现一条消息来告知原因。如果用户只想看到结果，并不知道要
# 分析哪些文件，可能就无须在有些文件不存在时告知他们。向用户
# 显示他不想看到的信息可能会降低程序的可用性。Python的错误处
# 理结构让你能够细致地控制与用户分享错误信息的程度，要分享多
# 少信息由你决定。
# 编写得很好且经过详尽测试的代码不容易出现内部错误，如语法或
# 逻辑错误，但只要程序依赖于外部因素，如用户输入、存在指定的
# 文件、有网络链接，就有可能出现异常。凭借经验可判断该在程序
# 的什么地方包含异常处理块，以及出现错误时该向用户提供多少相
# 关的信息。


